package service.facade.admin;

import application.exportimport.DatabaseImporter;
import dao.inte.*;
import domain.AbstractEntity;
import domain.ImageEntry;
import domain.User;
import domain.shop.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import service.ILuceneIndexRebuilder;
import service.mail.Mailer;
import service.velocity.VelocityService;
import web.admin.config.DirectoryPathException;

import javax.inject.Inject;
import javax.mail.MessagingException;
import javax.persistence.EntityNotFoundException;
import javax.xml.bind.JAXBException;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

@Transactional
public class ShopAdminServiceImpl implements ShopAdminService {

    private VelocityService velocityService;
    @Inject
    private DeliveryMethodRepository deliveryMethodRepository;
    @Inject
    private PayMethodRepository payMethodRepository;
    @Inject
    private Mailer mailer;
    @Inject
    private ConfigurationRepository configurationRepository;
    @Inject
    private ProductRepository productRepository;
    @Inject
    private CategoryRepository categoryRepository;
    @Inject
    private UserRepository userRepository;
    @Inject
    private ILuceneIndexRebuilder luceneIndexer;
    @Inject
    private ImageEntryDAO imageEntryDAO;
    @Inject
    AttributeRepository attributeRepository;
    @Inject
    private DatabaseImporter importer;

    public ShopAdminServiceImpl() {
    }

    public ShopAdminServiceImpl(DeliveryMethodRepository deliveryMethodRepository, PayMethodRepository payMethodRepository, Mailer mailer,
                                ConfigurationRepository configurationRepository, ProductRepository productRepository, CategoryRepository categoryRepository,
                                UserRepository userRepository, DatabaseImporter importer) {
        this.deliveryMethodRepository = deliveryMethodRepository;
        this.payMethodRepository = payMethodRepository;
        this.mailer = mailer;
        this.configurationRepository = configurationRepository;
        this.productRepository = productRepository;
        this.categoryRepository = categoryRepository;
        this.userRepository = userRepository;
        this.importer = importer;
    }

    public void setProductMainImage(Long productId, Long imageId) throws Exception {
        Product product = productRepository.findById(productId);
        ImageEntry imageEntry = imageEntryDAO.findById(imageId);
        product.setMainImage(imageEntry);
        productRepository.save(product);
    }

    public boolean activateAccount(User user) throws Exception {
        user.setEnabled(true);
        userRepository.save(user).isEnabled();
        sendActivationMail(user);
        Logger.getLogger(CmsAdminServiceImpl.class.getName()).log(Level.INFO, "ActivateAccount: " + user.getLogin());
        return true;
    }

    private void sendActivationMail(User user) throws DirectoryPathException, Exception, MessagingException {
        String recipients[] = {user.getCompanyEmail()};
        String subject = velocityService.getAccountActivationMail(user);
        String message = configurationRepository.getConfig().getAccountActivationMailSubject();
        String from = configurationRepository.getConfig().getAdministratorEmail();
        mailer.postMail(recipients, subject, message, from);
    }

    public void setProductToCategories(Long productId, Map<Long, Boolean> categories) throws Exception {
        for (Long categoryId : categories.keySet()) {

            if (categories.get(categoryId)) {
                addProductToCategory(productId, categoryId);
            } else {
                removeProductFromCategory(productId, categoryId);
            }
        }
    }

    private <T extends AbstractEntity> void reindexEntity(T entity) throws Exception {
        luceneIndexer.reindexEntity(entity);

    }

    public void removeProductFromCategory(Long productId, Long categoryId) throws Exception {
        Product p = productRepository.findById(productId);
        Category c = categoryRepository.findById(categoryId);
        removeProductFromCategory(p, c);
    }

    public void addProductToCategory(Long productId, Long categoryId) throws Exception {
        Product p = productRepository.findById(productId);
        Category c = categoryRepository.findById(categoryId);
        addProductToCategory(p, c);
    }

    public void addProductToCategory(Product product, Category category) throws Exception {
        product.addCategory(category);
        category.addProduct(product);
        productRepository.save(product);
        categoryRepository.save(category);
        reindexEntity(product);
    }

    public void removeProductFromCategory(Product product, Category category) throws Exception {
        product.removeCategory(category);
        category.removeProduct(product);
        productRepository.save(product);
        categoryRepository.save(category);
        reindexEntity(product);
    }

    public boolean deactivateAccount(User user) throws Exception {
        user.setEnabled(false);
        return userRepository.save(user).isEnabled();
    }

    public void updateProduct(Product product) throws Exception {
        productRepository.save(product);
        reindexEntity(product);
    }

    public Product findProductById(final Long Id) {
        return productRepository.findById(Id);
    }

    public Product createProduct() throws Exception {
        return productRepository.create();
    }

    public List<Product> findAllProducts() {
        return productRepository.findAll();
    }

    // todo attribute repo find max attribute index for product
    public Attribute addProductAttribute(Product product, Attribute attribute) throws Exception {
        attribute.setProduct(product);
        Integer index = 0;
        for (Attribute aa : product.getAttributes()) {
            if (aa.getIndex() > index) {
                index = aa.getIndex();
            }
        }

        product.addAttribute(attribute);
        productRepository.save(product);
        attributeRepository.save(attribute);
        reindexEntity(product);
        return attribute;
    }

    public List<Product> findProductsByIdLike(Long input) {
        return productRepository.findProductsByIdLike(input);
    }

    public Iterable<Product> findProducts(int first, int count, String property, boolean ascending) {
        return productRepository.findProducts(first, count, property, ascending);
    }

    public int getProductsCount() {
        return productRepository.count();
    }

    public List<User> findUsers(int first, int count, String property, boolean ascending) {
        return userRepository.findUsers(first, count, property, ascending);
    }

    public User findUserById(Long id) {
        return userRepository.findById(id);
    }

    public int getUsersCount() {
        return userRepository.count();
    }

    @Deprecated
    public void addProductAttribute(Product product) throws Exception {
        Attribute a = new Attribute();
        addProductAttribute(product, a);
    }

    public void deleteProductAttribute(Product product, Attribute attribute) throws Exception {
        product.removeAttribute(attribute);
        attribute.setProduct(null);
        productRepository.save(product);
        reindexEntity(product);
    }

    public Set<Long> getCategoryIdsByProduct(Long productId) {
        Set<Category> categorySet = productRepository.findById(productId).getCategories();
        Set<Long> longSet = new HashSet<Long>();
        for (Category category : categorySet) {
            longSet.add(category.getId());
        }
        return longSet;
    }

    public List<Category> getRootCategories() {
        return categoryRepository.findRoots();
    }

    public void deleteProduct(Product product) throws Exception {
        product = productRepository.findById(product.getId());
        if (product == null)
            throw new EntityNotFoundException();
        for (Category cat : new HashSet<Category>(product.getCategories())) {
            cat.removeProduct(product);
            categoryRepository.save(cat);
            product.removeCategory(cat);
            // categoryRepository.save(cat);
        }
        for (Product p : new HashSet<Product>(product.getRelatedProducts())) {
            product.removeRelatedProduct(p);
            p.removeRelatedProduct(product);
        }
        for (Product p : new HashSet<Product>(product.getComponentTo())) {
            p.removeComponentProduct(product);

        }
        for (Product p : new HashSet<Product>(product.getComponents())) {
            p.removeComponentProduct(product);

        }
        productRepository.delete(product);
    }

    public Set<Product> findRelatedProductsByProductId(final Long productId) {
        return productRepository.findRelatedProductsByProductId(productId);
    }

    public Category createCategoryAsChild(Category category) throws Exception {
        return categoryRepository.createChild(category);
    }

    public Category findCategoryById(final Long Id) {
        return categoryRepository.findById(Id);
    }

    public Category createCategory() throws Exception {
        return categoryRepository.create();
    }

    public void deleteCategory(Category category) {
        category = categoryRepository.findById(category.getId());
        if (category == null)
            throw new EntityNotFoundException();
        for (Product product : category.getProducts()) {
            product.removeCategory(category);
        }
        categoryRepository.delete(category);
    }

    public void updateCategory(Category category) throws Exception {
        categoryRepository.save(category);
    }

    public List<Category> findCategoryRoots() {
        return categoryRepository.findRoots();
    }

    public List<Category> findCategoryChildsByParentId(final Long Id) {
        return categoryRepository.findChildsByParentId(Id);
    }

    public DeliveryMethod findDeliveryMethodById(final Long Id) {
        return deliveryMethodRepository.findById(Id);
    }

    public void deleteDeliveryMethod(DeliveryMethod deliveryMethod) {
        deliveryMethodRepository.delete(deliveryMethod);
    }

    public void updateDeliveryMethod(DeliveryMethod deliveryMethod) throws Exception {
        deliveryMethodRepository.save(deliveryMethod);
    }

    public List<DeliveryMethod> findAllDeliveryMethods() {
        return deliveryMethodRepository.findAll();
    }

    public DeliveryMethod createDeliveryMethod() throws Exception {
        return deliveryMethodRepository.create();
    }

    public void deletePayMethod(PayMethod payMethod) {
        payMethodRepository.delete(payMethod);
    }

    public void updatePayMethod(PayMethod payMethod) throws Exception {
        payMethodRepository.save(payMethod);
    }

    public PayMethod createPayMethod() throws Exception {
        return payMethodRepository.create();
    }

    public PayMethod findPayMethodById(final Long Id) {
        return payMethodRepository.findById(Id);
    }

    public List<PayMethod> findAllPayMethods() {
        return payMethodRepository.findAll();
    }

    public void setRelatedProducts(Long mainProductId, Set<Long> relatedProducts) throws Exception {
        Product product = productRepository.findById(mainProductId);

        final Set<Product> oldRelatedProducts = new HashSet<Product>(product.getRelatedProducts());
        for (Long productId : relatedProducts) {
            Product foo = productRepository.findById(productId);
            product.addRelatedProduct(foo);
            foo.addRelatedProduct(product);

            oldRelatedProducts.remove(foo);

        }
        for (Product oldRelatedProduct : oldRelatedProducts) {
            product.removeRelatedProduct(oldRelatedProduct);
            oldRelatedProduct.removeRelatedProduct(product);
        }
        productRepository.save(product);

        reindexEntity(product);
    }

    public void setComponentsOfProduct(Long mainProductId, Set<Long> componentsOfProducts) throws Exception {
        Product product = productRepository.findById(mainProductId);

        final List<Product> oldComponents = new ArrayList<Product>(product.getComponents());
        for (Long productId : componentsOfProducts) {
            Product foo = productRepository.findById(productId);
            product.addComponentProduct(foo);
            foo.addComponentToProduct(product);
            oldComponents.remove(foo);
        }
        for (Product oldProduct : oldComponents) {
            product.removeComponentProduct(oldProduct);
            oldProduct.removeComponentToProduct(product);
        }
        productRepository.save(product);
        reindexEntity(product);
    }

    public void addImageToProduct(Product product, ImageEntry image) throws Exception {
        product.addImage(image);
        if (product.getMainImage() == null) {
            product.setMainImage(image);
        }
        updateProduct(product);
    }

    @Autowired(required = true)
    public void setVelocityService(VelocityService velocityService) {
        this.velocityService = velocityService;
    }

    public VelocityService getVelocityService() {
        return velocityService;
    }

    public void importProductsFromXML(InputStream is) throws JAXBException, FileNotFoundException, Exception {
        importer.importProducts(is);
    }
}
